package engine.systems.spatial.sleep
{
	import ash.core.Engine;
	import ash.core.Entity;
	import ash.core.NodeList;
	import ash.core.System;
	
	import engine.GameEngine;
	import engine.classes.enumerations.SystemPriority;
	import engine.managers.ScreenManager;
	import engine.systems.group.Children;
	import engine.systems.render.camera.CameraNode;
	import engine.systems.update.GameSystem;
	
	import flash.display.Stage;
	import flash.geom.Rectangle;
	
	/**
	 * The Sleep System puts entities to sleep that are off-screen to reduce the amount of Entities that need
	 * to be processed by other systems during an update. Any system that extends Game System does a check
	 * on an Entity to see if it's sleeping or paused.
	 * 
	 * @see Sleep
	 * @see SleepNode
	 */
	public class SleepSystem extends System
	{
		private var sleeps:NodeList;
		private var cameras:NodeList;
		
		private var viewport:Rectangle = new Rectangle();
		
		public function SleepSystem()
		{
			this.priority = SystemPriority.SLEEP;
		}
		
		override public function update(time:Number):void
		{
			//There should only be one Camera at any given time, so getting the head is the only thing needed.
			var camera:CameraNode = this.cameras.head;
			if(camera)
			{
				var rect:Rectangle = camera.camera.viewport;
				this.viewport.setTo(rect.x, rect.y, rect.width, rect.height);
			}
			else
			{
				var stage:Stage = GameEngine.screenManager.stage;
				this.viewport.setTo(0, 0, stage.stageWidth, stage.stageHeight);
			}
			
			for(var node:SleepNode = this.sleeps.head; node; node = node.next)
			{
				node.entity.ignoreGroup = node.sleep.ignoreGroup;
				
				if(node.entity.group.paused && !node.entity.ignoreGroup)
				{
					this.setSleep(node.entity, true, true, true);
				}
				else
				{
					if(!node.sleep.ignoreOffscreen)
					{
						switch(node.sleep.type)
						{
							case Sleep.TYPE_DISPLAY:
								this.checkDisplay(node);
								break;
							
							case Sleep.TYPE_SPATIAL:
								this.checkSpatial(node);
								break;
						}
					}
					else this.setSleep(node.entity, node.sleep.sleeping);
				}
				
			}
		}
		
		private function checkDisplay(node:SleepNode):void
		{
			if(node.render)
			{
				node.sleep.sleeping = !GameEngine.screenManager.sleepDisplay.hitTestObject(node.render.display);
				this.setSleep(node.entity, node.sleep.sleeping);
			}
			else this.checkSpatial(node);
		}
		
		private function checkSpatial(node:SleepNode):void
		{
			var left:Number 	= 0;
			var right:Number 	= 0;
			var top:Number 		= 0;
			var bottom:Number 	= 0;
			
			if(node.edge)
			{
				left 	= node.edge.left;
				right 	= node.edge.right;
				top 	= node.edge.top;
				bottom 	= node.edge.bottom;
			}
			
			node.sleep.sleeping = false;
			if(node.spatial.x + right < this.viewport.left) 		node.sleep.sleeping = true;
			else if(node.spatial.x + left > this.viewport.right)	node.sleep.sleeping = true;
			else if(node.spatial.y + bottom < this.viewport.top)	node.sleep.sleeping = true;
			else if(node.spatial.y + top > this.viewport.bottom)	node.sleep.sleeping = true;
			
			this.setSleep(node.entity, node.sleep.sleeping);
		}
		
		private function setSleep(entity:Entity, sleeping:Boolean, managedSleep:Boolean = true, paused:Boolean = false):void
		{
			if(entity.sleeping == sleeping) return;
			
			entity.sleeping 	= sleeping;
			entity.managedSleep = managedSleep;
			entity.paused 		= paused;
			
			var children:Children = entity.get(Children);
			if(!children) return;
			
			for each(var child:Entity in children.children)
			{
				if(child.get(Sleep)) continue;
				this.setSleep(child, sleeping, managedSleep, paused);
			}
		}
		
		override public function addToEngine(systemManager:Engine):void
		{
			this.cameras 	= systemManager.getNodeList(CameraNode);
			this.sleeps 	= systemManager.getNodeList(SleepNode);
			
			for(var node:SleepNode = this.sleeps.head; node; node = node.next) this.nodeAdded(node);
			this.sleeps.nodeAdded.add(nodeAdded);
			this.sleeps.nodeRemoved.add(nodeRemoved);
		}
		
		override public function removeFromEngine(systemManager:Engine):void
		{
			systemManager.releaseNodeList(SleepNode);
			/**
			 * We're not releasing the CameraNode NodeList because that's what the Camera System
			 * is going to do when it's done with its NodeList.
			 */
			
			this.sleeps 	= null;
			this.cameras 	= null;
		}
		
		private function nodeAdded(node:SleepNode):void
		{
			this.setSleep(node.entity, false, true);
		}
		
		private function nodeRemoved(node:SleepNode):void
		{
			this.setSleep(node.entity, false, false);
		}
	}
}